<template>
	<view v-if="showPopup" class="vi_popup" :style="{'z-index': setting.zIndex}" @touchmove.stop.prevent="clearProp">
		<vi-transition v-if="setting.useMask&&maskState"
			mode="fade"
			:styles="maskStyle"
			:forceDuration="duration"
			:show="showTrans"
			@click="onMaskClick" />
		<vi-transition
			:mode="transMode"
			:styles="transStyle"
			:forceDuration="duration"
			:show="showTrans"
			@click="onMaskClick"
			@done="transDone">
			<view class="vi_popup_wrapper" :style="wrapperStyle" @click.stop="clearProp">
				<slot />
			</view>
		</vi-transition>
	</view>
</template>

<script>

export default {
	name: "viPopup",
	props: {
		// 开启动画
		animation:{
			type:[Boolean,null],
			default:null
		},
		animationMode:{
			type:[String,null],
			default:""
		},
		//弹出位置，top顶部弹出、bottom底部弹出、center中部弹出、left左侧弹出、right右侧弹出
		point:{
			type:[String,null],
			default:null
		},
		useMask:{
			type:[Boolean,null],
			default:null
		},
		//点击遮罩关闭
		maskClick:{
			type:[Boolean,null],
			default:null
		},
		//圆角
		radius:{
			type:[String,Number,Array,null],
			default:""
		},
		bgColor:{
			type:[String,null],
			default:null
		},
		maskBgColor:{
			type:[String,null],
			default:""
		},
		//适配顶部安全区域（留出默认头部导航条距离），true：一直适配。false：一直关闭。数字：窗口宽高均小于数字时适配，否则关闭
		safeAreaTop:{
			type:[Boolean,Number,null],
			default:null
		},
		//适配底部安全区域（留出默认底部tab导航距离）
		safeAreaBottom:{
			type:[Boolean,null],
			default:null
		},
		zIndex:{
			type:[Number,null],
			default:null
		},
		//内容板块默认宽，自定义单位
		//""表示不设置，point为left、right时默认auto；top、bottom时默认100%
		width:{
			type:[String,null],
			default:""
		},
		//内容板块默认高，自定义单位
		//""表示不设置，point为left、right时默认100%；top、bottom时默认auto
		height:{
			type:[String,null],
			default:""
		},
		//手动偏移，限制popup的显示区，独立于safeArea设置，用于自定义导航及自定义tab栏等
		//可带单位，默认单位px。间隙部分会观测到动画断层，还需配合zIndex让外层内容遮住间隙部分
		//状态栏的占位计算应在外部执行
		shift:{
			type:[Object,null],
			default: ()=>({
				// left:0,
				// right:0,
				// top:0,
				// bottom:0
			})
		},
		//节流，动画完成前重复点击无效
		useClickLock:{
			type:[Boolean,null],
			default:null
		}
	},
	data() {
		return {
			duration: 0,
			transMode: "",
			showPopup: false,
			showTrans: false,
			transitioning: false,	//过渡中，开启点击锁时，动画结束才能再次切换状态
			inPoint:"",
			winInfo:{
				width: 0,
				height: 0,
				top: 0,
				bottom: 0
			},
			transStyleObj:{
				position: "fixed",
				left: 0,
				right: 0
			},
			innerType: ["top", "center", "bottom", "left", "right"],
			maskState: true
		}
	},
	computed: {
		//将默认值内含到css、计算属性等地方，只内含真值
		//规避特殊使用的情况下，由prop传入undefined、null等无效数据导致的，默认值失效问题。例如组件嵌套且需要组件间参数传递
		//HBuilder X3.3.13; 微信开发者工具stable 1.05.2111300
		//微信小程序会对传入的undefined、null值根据参数类型输出不同空值(false、""、null、0)，参数类型为null或未指定参数类型则输出null
		//H5会对传入的undefined取默认值，未设默认值则输出undefined。对传入的null值固定输出null
		//做参数传递时需警惕两者行为不同而造成的陷阱，主要问题出现在微信小程序的空值转化，输出非用户传入的空值、假值，使默认真值丢失
		//兼容空值取默认值的可行方案目前为，参数类型加入null如[Boolean,null]，判断传入参数是否为定义的类型，是则使用，否则赋予默认值
		setting(){
			return {
				useMask: this.$api.isBoolean(this.useMask)? this.useMask :true,
				safeAreaBottom: this.$api.isBoolean(this.safeAreaBottom)? this.safeAreaBottom :true,
				safeAreaTop: this.$api.isDefined(this.safeAreaTop)? this.safeAreaTop :500,
				zIndex: this.$api.isValue(this.zIndex)? this.zIndex :99,
				useClickLock: this.$api.isBoolean(this.useClickLock)? this.useClickLock :true,
				animation: this.$api.isBoolean(this.animation)? this.animation :true,
				maskClick: this.$api.isBoolean(this.maskClick)? this.maskClick :true,
				bgColor: this.$api.isDefined(this.bgColor)? this.bgColor :"#fff",
				maskBgColor: this.maskBgColor || "rgba(0, 0, 0, 0.4)",
			}
		},
		bgValue(){
			let bgColor = this.setting.bgColor;
			if(!bgColor || bgColor=="none"){
				return "transparent"
			}
			return bgColor
		},
		safeObj(){
			let res = {};
			let shift = this.shift ||{};
			if(["top","left","right"].includes(this.inPoint)){
				let safeAreaTop = this.setting.safeAreaTop;
				if(safeAreaTop == true || (typeof(safeAreaTop) == "number" && this.winInfo.width <= safeAreaTop && this.winInfo.height <= safeAreaTop) ){
					res["top"] = this.winInfo.top + "px"
				}
			}
			if(this.inPoint == "bottom"){
				if(this.setting.safeAreaBottom){
					res["bottom"] = this.winInfo.bottom + "px"
				}
			}
			//手动偏移，支持calc
			let direction = ["top","bottom", "left", "right"];
			for(let dir of direction){
				if(shift[dir]){
					let shiftVal = shift[dir],
						resVal = res[dir];
					shiftVal += this.$api.isNumber(shiftVal,true)? "px":"";
					if(resVal){
						if(shiftVal.startsWith("calc(")){
							shiftVal = this.$api.setCalc(shiftVal,resVal)
						}else{
							shiftVal = `calc(${[resVal,shiftVal].join(" + ")})`
						}
					}
					res[dir] = shiftVal
				}
			}
			return styleObj
		},
		maskStyle(){
			let styleObj = {
				"position": "fixed",
				"bottom": 0,
				"top": 0,
				"left": 0,
				"right": 0,
				"background-color": this.setting.maskBgColor
			}
			return Object.assign(styleObj, this.safeObj)
		},
		transStyle(){
			return Object.assign(this.transStyleObj, this.safeObj)
		},
		wrapperStyle(){
			let styleObj = {
				"background-color": this.bgValue
			}
			//设置默认宽度
			if(this.width){
				if(["left","right"].includes(this.inPoint)&&this.width.endsWith("%")){
					styleObj["width"] = this.$api.splitUnit(this.width)[0] +"vw"
				}else{
					styleObj["width"] = this.width
				}
			}else if(["top","bottom"].includes(this.inPoint)){
				styleObj["width"] = "100%"
			}
			//设置默认高度
			if(this.height){
				if(["top","bottom"].includes(this.inPoint)&&this.height.endsWith("%")){
					let reduceNum = 0;
					let reduceStr = "";
					//带calc字符需提取
					let safeTop = this.safeObj.top;
					if(safeTop){
						if(safeTop.includes("calc")){
							reduceStr += " - " + safeTop.match(/calc(.*)/)[1];
						}else if(!safeTop.includes("rpx") && safeTop.endsWith("px")){
							reduceNum += this.$api.splitUnit(safeTop)[0]
						}else{
							reduceStr += " - " + safeTop
						}
					}
					let safeBottom = this.safeObj.bottom;
					if(safeBottom){
						if(safeBottom.includes("calc")){
							reduceStr += " - " + safeBottom.match(/calc(.*)/)[1]
						}else if(!safeBottom.includes("rpx") && safeBottom.endsWith("px")){
							reduceNum += this.$api.splitUnit(safeBottom)[0]
						}else{
							reduceStr += " - " + safeBottom
						}
					}
					styleObj["height"] = `calc(${this.$api.splitUnit(this.height)[0]}vh - ${reduceNum}px${reduceStr})`
				}else{
					styleObj["height"] = this.height
				}
			}else if(["left","right"].includes(this.inPoint)){
				styleObj["height"] = "100%"
			}
			//设置圆角
			let radius = this.radius;
			if(this.$api.isArray(radius)){
				let lt = radius[0] ||0,
					rt = radius[1] ||0,
					rb = radius[2] ||0,
					lb = radius[3] ||0;
				styleObj["border-radius"] = `${lt} ${rt} ${rb} ${lb}`;
			}else if(radius){
				styleObj["border-radius"] = radius
			}
			return this.$api.objToStr(styleObj)
		},
	},
	methods: {
		//取消冒泡
		clearProp(e) {
			// #ifndef APP-NVUE
			e.stopPropagation()
			// #endif
			this.clearPropagation = true
		},
		open(direction, animationMode){
			if(this.showTrans||(this.setting.useClickLock&&this.transitioning)){
				return
			}
			this.transitioning = true;
			if(direction && this.innerType.indexOf(direction) !== -1){
				this.inPoint = direction
			}else{
				direction = this.inPoint
			}
			let styleObj ={
				"position": "fixed",
				"top":0,
				"bottom": 0,
				"left": 0,
				"right": 0,
				"display": "flex",
				"flex-direction": "column",
				"justify-content": "center",
				"align-items": "center"
			}
			switch(direction){
				case "top":
					this.transMode = "slideTop";
					delete styleObj.bottom;
					styleObj["justify-content"] ="flex-start";
					this.transStyleObj = styleObj;
					break;
				case "center":
					this.transMode = "zoomIn";
					this.transStyleObj = styleObj;
					break;
				case "bottom":
					this.transMode = "slideBottom";
					delete styleObj.top;
					styleObj["justify-content"] ="flex-end";
					styleObj["padding-bottom"] = this.setting.safeAreaBottom? this.winInfo.bottom :0;
					this.transStyleObj = styleObj;
					break;
				case "left":
					this.transMode = "slideLeft";
					delete styleObj.right;
					this.transStyleObj = styleObj;
					break;
				case "right":
					this.transMode = "slideRight";
					delete styleObj.left;
					this.transStyleObj = styleObj;
					break;
				default:
					console.log("popup打开参数错误");
					return
			}
			if(this.animationMode){
				this.transMode = this.animationMode
			}
			if(this.$api.isString(animationMode)){
				this.transMode = animationMode
			}
			this.showPopup = true;
			this.showTrans = true;
			this.$emit("change", {
				show: true,
				point: direction
			})
		},
		close(){
			if(!this.showTrans||(this.setting.useClickLock&&this.transitioning)){
				return
			}
			this.transitioning = true;
			this.showTrans = false;
			this.$emit("change", {
				show: false,
				point: this.inPoint
			})
		},
		transDone(e){
			this.transitioning = false;
			//动画完毕事件
			if(!e && !this.showTrans){//已关闭
				this.showPopup = false;
				this.$emit("closed")
			}else if(e){
				this.$emit("opened")
			}
		},
		closeMask(){
			this.maskState = false
		},
		openMask(){
			this.maskState = true
		},
		onMaskClick(){
			this.$emit("maskClick")
			if(this.setting.maskClick){
				this.close()
			}
		},
		getScreenInfo(){
			try {
				const res = uni.getSystemInfoSync();
				Object.assign(this.winInfo, {
					width: res.windowWidth,
					height: res.windowHeight,
					top: res.windowTop ||0,
					bottom: res.windowBottom ||0
				})
			} catch (e) {
				console.log("获取系统信息失败",e);
			}
		}
	},
	watch:{
		"point":{
			immediate: true,
			handler(newVal){
				this.inPoint = newVal ||"center";
			}
		}
	},
	created() {
		this.duration = this.setting.animation? 300 :0
	},
	mounted() {
		this.getScreenInfo();
	},
}
</script>
<style lang="scss">
.vi_popup {
	position: fixed;
	.vi_popup_wrapper {
		display: block;
		position: relative;
	}
}
</style>
